﻿/*
    MAST -- A file Merging and Splitting Tool
    Copyright (C) 2011 Harry Rose

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

namespace FileMergeAndSplitTool
{
    public partial class MASTMainForm : Form
    {
        public enum PartIDs
        {
            Ignore,
            PartNumber,
            FilenameBase
        }

        RegexBuilder<PartIDs> regexBuilder = new RegexBuilder<PartIDs>();
        TokenHelpWindow tokenHelpWindow;

        private void OpenTokenHelpWindow()
        {
            if (tokenHelpWindow == null || tokenHelpWindow.IsDisposed)
            {
                tokenHelpWindow = new TokenHelpWindow();
            }

            if (tokenHelpWindow.Visible)
            {
                tokenHelpWindow.BringToFront();
                return;
            }

            tokenHelpWindow.Descriptions = regexBuilder.TokenDescriptors;
            tokenHelpWindow.Show(this);
            tokenHelpWindow.BringToFront();
        }

        private void CloseTokenHelpWindow()
        {
            if (tokenHelpWindow == null || tokenHelpWindow.IsDisposed) return;

            tokenHelpWindow.Close();
        }

        public void populateTokens()
        {
            regexBuilder.AddToken("%file%","(.+)","Base filename of split file",PartIDs.FilenameBase);
            regexBuilder.AddToken("%part%", "([0-9]+)", "Part number", PartIDs.PartNumber);
            regexBuilder.AddToken("*", ".*", "Wildcard", PartIDs.Ignore);
        }

        public MASTMainForm()
        {
            populateTokens();
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            LicenseTextHolder gpl = new LicenseTextHolder();
            GPLTextBox.Text = gpl.License;
        }

       

        private void OpenDirectoryDialogButton_Click(object sender, EventArgs e)
        {
            MergeInputDirectoryBrowser.Description = ("Locate the directory containing the files to be merged");
            if (DialogResult.OK == MergeInputDirectoryBrowser.ShowDialog(this))
            {
                MergeInputDirectoryTextBox.Text = MergeInputDirectoryBrowser.SelectedPath;
            }
        }

        void populateFileList(List<MergeableFile> files)
        {
            MatchesListView.Groups.Clear();
            MatchesListView.Items.Clear();
            foreach (MergeableFile mf in files)
            {
                addToFileList(mf);
            }
         
        }

        void addToFileList(MergeableFile file)
        {
            ListViewGroup lvg = new ListViewGroup(file.OutputFilename);
            MatchesListView.Groups.Add(lvg);
            foreach(FilePart part in file.Parts)
            {
                ListViewItem lvi = new ListViewItem(lvg);
                lvi.Text = part.Part+"";
                lvi.Name = part.Name;
                //lvi.SubItems.Add(part.Part+"");
                lvi.SubItems.Add(part.Name);
                lvi.SubItems.Add(file.OutputFilename);
                lvi.Group = lvg;
                lvg.Tag = file;
                MatchesListView.Items.Add(lvi);
            }
        }

        public List<MergeableFile> FindAllMatchedFiles(string directory)
        {
            regexBuilder.PatternString = PatternTextBox.Text;
            Dictionary<string, MergeableFile> baseToMFMap = new Dictionary<string, MergeableFile>();
            DirectoryInfo dir = new DirectoryInfo(directory);
            foreach (FileInfo file in dir.GetFiles())
            {
                Dictionary<PartIDs, List<string>> parsed = regexBuilder.Parse(file.Name);
                if (parsed[PartIDs.FilenameBase].Count <= 0)
                {
                    continue;
                }

                string baseFilename = string.Join("", parsed[PartIDs.FilenameBase]);
                int partNumber = Int32.Parse(string.Join("",parsed[PartIDs.PartNumber]));

                if (!baseToMFMap.ContainsKey(baseFilename))
                {
                    baseToMFMap.Add(baseFilename, new MergeableFile((new FileInfo(Path.Combine(file.Directory.FullName, baseFilename))).FullName));

                }

                baseToMFMap[baseFilename].AddPart(file.FullName, partNumber);
            }

            return new List<MergeableFile>(baseToMFMap.Values);
        }

        private void FindButton_Click(object sender, EventArgs e)
        {
            if(ValidateMergeInputDirectory())
            {
                populateFileList(FindAllMatchedFiles(MergeInputDirectoryTextBox.Text));

                MergeButton.Enabled = (MatchesListView.Items.Count > 0);
            }
        }

        private bool ValidateMergeInputDirectory()
        {
            try
            {
                DirectoryInfo dir = new DirectoryInfo(MergeInputDirectoryTextBox.Text);
                if (dir.Exists)
                    return true;
                else
                    MessageBox.Show(this, "The input directory does not exist.", "Input directory not found", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                
            }
            catch (ArgumentException)
            {
                MessageBox.Show(this, "The input directory path isn't valid.", "Invalid input directory", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
            }
            return false;
        }

        private void MergeButton_Click(object sender, EventArgs e)
        {
            
            MergeBackgroundWorker.RunWorkerAsync();
        }

        private void MergeBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            List<MergeableFile> files = new List<MergeableFile>();
            this.Invoke((MethodInvoker)delegate() { MergeProgressBar.Value = 0; });
            progress = 0;
            foreach (ListViewGroup grp in MatchesListView.Groups)
            {
                files.Add((MergeableFile)grp.Tag);
            }
            int i =0;
            jobSize = files.Count;
            foreach (MergeableFile mf in files)
            {
                mf.ProgressUpdate += MergeProgress;
                mf.Merge(mf.OutputFilename);
                progress = (100.0 * (++i)) / (double)jobSize; 
            }
            this.Invoke((MethodInvoker)delegate() { StatusLabel.Text = "Complete"; });

        }

        public void MergeProgress(MergeableFile mergeableFile, string currentPart, double percentComplete)
        {
            this.Invoke((MethodInvoker) delegate() { MergeProgressBar.Value = (int)(progress + percentComplete / (double)jobSize);});
            this.Invoke((MethodInvoker) delegate() { StatusLabel.Text = "Merging " + mergeableFile.OutputFilename + ", " + currentPart; });
        }

        double progress = 0;
        int jobSize = 1;

        private void MergePatternHelpButton_Click(object sender, EventArgs e)
        {
            OpenTokenHelpWindow();
        }

        private void SplitOutputDialogButton_Click(object sender, EventArgs e)
        {
            if (MergeInputDirectoryBrowser.ShowDialog(this) == DialogResult.OK)
            {
                SplitOutputDirTextBox.Text = MergeInputDirectoryBrowser.SelectedPath;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (OpenSplitFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                InputFileTextBox.Text = OpenSplitFileDialog.FileName;
            }
        }

        private void SplitButton_Click(object sender, EventArgs e)
        {
            if (InputFileTextBox.Text.Trim().Length == 0)
            {
                MessageBox.Show(this, "Please specify an input file", "Input file not specified", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                return;
            }

            if (SplitOutputDirTextBox.Text.Trim().Length == 0)
            {
                MessageBox.Show(this, "Please specify an output directory", "Output directory not specified", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                return;
            }

            if (!File.Exists(InputFileTextBox.Text))
            {
                MessageBox.Show(this, "The specified input file does not exist", "Input file not found", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                return;
            }
            else if(!Directory.Exists(SplitOutputDirTextBox.Text))
            {
                if (DialogResult.Yes == MessageBox.Show(this, "The specified output directory does not exist.  Should I create it?", "Output directory not found", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
                {
                    try
                    {
                        Directory.CreateDirectory(SplitOutputDirTextBox.Text);
                    }
                    catch (Exception)
                    {
                        MessageBox.Show(this, "Unable to create output directory.", "Cannot create output directory", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        return;
                    }
                }
                else
                {
                    return;
                }
            }

            SplitBackgroundWorker.RunWorkerAsync();
        }

        public void SplitProgress(SplittableFile splittableFile, string currentPart, double percentComplete)
        {
            this.Invoke((MethodInvoker)delegate() { SplitProgressBar.Value = (int)(percentComplete); });
            this.Invoke((MethodInvoker)delegate() { SplitStatusLabel.Text = "Splitting " + splittableFile.FullName + ", " + currentPart; });
        }

        private void SplitBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            SplittableFile file = new SplittableFile(   InputFileTextBox.Text,
                                                        SplitOutputDirTextBox.Text,
                                                        MakePatternStringSplitterCompatible(SplitFilePatternTextBox.Text),
                                                        (int)FileSizeSelect.Value
                                                        );

            file.ProgressUpdate += SplitProgress;
            file.Split();
            this.Invoke((MethodInvoker)delegate() { SplitStatusLabel.Text = "Complete"; });
        }

        private string MakePatternStringSplitterCompatible(string patternString)
        {
            patternString = regexBuilder.ReplaceSemanticToken(patternString, PartIDs.FilenameBase, new FileInfo(InputFileTextBox.Text).Name);
            patternString = regexBuilder.ReplaceSemanticToken(patternString, PartIDs.PartNumber, SplittableFile.PartToken);

            return patternString;
        }

        private void MASTMainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            CloseTokenHelpWindow();
        }

        private void GPLTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.PageDown:
                case Keys.PageUp:
                case Keys.Up:
                case Keys.Down:
                    //Do nothing
                    break;

                default:
                    e.Handled = true;
                    break;
            }
        }

        private void MastWebsiteLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            ProcessStartInfo sInfo = new ProcessStartInfo(MastWebsiteLink.Text);
            Process.Start(sInfo);
        }
    }
}